package controller;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.fasterxml.jackson.databind.ObjectMapper;

@Controller
public class IntegrationController {

    @Resource(name = "objectMapper")
    private ObjectMapper jsonParser;

    @Resource(name = "outboundRules")
    private BlockingQueue<Map<String, Object>> rules;

    @Resource(name = "callsPerSec")
    private BlockingQueue<Map<String, Long>> callsPerSec;

    @Resource(name = "durationsPerSec")
    private BlockingQueue<Map<String, Double>> durationsPerSec;

    @Resource(name = "failedCallsPerSec")
    private BlockingQueue<Map<String, Long>> failedCallsPerSec;

    @Resource(name = "ruleResult")
    private BlockingQueue<Map<String, Object>> ruleResult;

    @PostConstruct
    public void init() {
        new Thread(new IntegrationTask()).start();
    }

    @RequestMapping(value = "/result/callsPerSec", method = RequestMethod.GET)
    public String showCallsPerSecPage() {
        return "callsPerSec";
    }

    @RequestMapping(value = "/result/durationsPerSec", method = RequestMethod.GET)
    public String showDurationsPerSecPage() {
        return "durationsPerSec";
    }

    @RequestMapping(value = "/result/failedCallsPerSec", method = RequestMethod.GET)
    public String showFailedCallsPerSecPage() {
        return "failedCallsPerSec";
    }

    @RequestMapping(value = "/result/rule", method = RequestMethod.GET)
    public String showRuleResultPage() {
        return "ruleResult";
    }

    @RequestMapping(value = "/result/callsPerSec", method = RequestMethod.POST)
    @ResponseBody
    public Object getCallsPerSec() {
        try {
            Object result = callsPerSec.poll(1000, TimeUnit.MILLISECONDS);
            if (result == null) {
                result = new HashMap<String, Long>();
            }
            return result;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @RequestMapping(value = "/result/durationsPerSec", method = RequestMethod.POST)
    @ResponseBody
    public Object getDurationsPerSec() {
        try {
            Object result = durationsPerSec.poll(1000, TimeUnit.MILLISECONDS);
            if (result == null) {
                result = new HashMap<String, Long>();
            }
            return result;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @RequestMapping(value = "/result/failedCallsPerSec", method = RequestMethod.POST)
    @ResponseBody
    public Object getFailedCallsPerSec() {
        try {
            Object result = failedCallsPerSec.poll(1000, TimeUnit.MILLISECONDS);
            if (result == null) {
                result = new HashMap<String, Long>();
            }
            return result;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @RequestMapping(value = "/result/rule", method = RequestMethod.POST)
    @ResponseBody
    public List<Object> getRuleResult() {
        final List<Object> result = new ArrayList<Object>(500);
        synchronized (ruleResult) {
            ruleResult.drainTo(result, 500);
        }
        return result;
    }

    private final class IntegrationTask implements Runnable {

        @Override
        public void run() {
            try {
                final ServerSocket serverSocket = new ServerSocket(8181);

                for (int i = 0; i < 5; i++) {
                    final Socket socket = serverSocket.accept();
                    final BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    final String role = reader.readLine();
                    if ("rule".equals(role)) {
                        new Thread(new SendRuleTask(socket, rules)).start();
                    } else if ("ruleResult".equals(role)) {
                        new Thread(new ReadRuleResultTask(socket, ruleResult)).start();
                    } else if ("callsPerSec".equals(role)) {
                        new Thread(new ReadCallsPerSecTask(socket, callsPerSec)).start();
                    } else if ("durationsPerSec".equals(role)) {
                        new Thread(new ReadDurationsPerSecTask(socket, durationsPerSec)).start();
                    } else if ("failedCallsPerSec".equals(role)) {
                        new Thread(new ReadFailedCallsPerSecTask(socket, failedCallsPerSec)).start();
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    private final class SendRuleTask implements Runnable {

        private final Socket socket;

        private final BlockingQueue<Map<String, Object>> rules;

        private SendRuleTask(Socket ruleSocket, BlockingQueue<Map<String, Object>> rules) {
            super();
            this.socket = ruleSocket;
            this.rules = rules;
        }

        @Override
        public void run() {
            try {
                final PrintWriter writer = new PrintWriter(socket.getOutputStream());
                while (true) {
                    writer.println(jsonParser.writeValueAsString(this.rules.take()));
                    writer.flush();
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    private final class ReadCallsPerSecTask implements Runnable {

        private final Socket socket;

        private final BlockingQueue<Map<String, Long>> output;

        private ReadCallsPerSecTask(Socket callsPerSecSocket, BlockingQueue<Map<String, Long>> callsPerSec) {
            super();
            this.socket = callsPerSecSocket;
            this.output = callsPerSec;
        }

        @Override
        public void run() {
            try {
                final BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                while (true) {
                    output.put(jsonParser.readValue(reader.readLine(), Map.class));
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    private final class ReadDurationsPerSecTask implements Runnable {

        private final Socket socket;

        private final BlockingQueue<Map<String, Double>> output;

        private ReadDurationsPerSecTask(Socket socket, BlockingQueue<Map<String, Double>> durationsPerSec) {
            super();
            this.socket = socket;
            this.output = durationsPerSec;
        }

        @Override
        public void run() {
            try {
                final BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                while (true) {
                    output.put(jsonParser.readValue(reader.readLine(), Map.class));
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    private final class ReadFailedCallsPerSecTask implements Runnable {

        private final Socket socket;

        private final BlockingQueue<Map<String, Long>> output;

        private ReadFailedCallsPerSecTask(Socket socket, BlockingQueue<Map<String, Long>> output) {
            super();
            this.socket = socket;
            this.output = output;
        }

        @Override
        public void run() {
            try {
                final BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                while (true) {
                    output.put(jsonParser.readValue(reader.readLine(), Map.class));
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    private final class ReadRuleResultTask implements Runnable {

        private final Socket socket;

        private final BlockingQueue<Map<String, Object>> output;

        private ReadRuleResultTask(Socket socket, BlockingQueue<Map<String, Object>> output) {
            super();
            this.socket = socket;
            this.output = output;
        }

        @Override
        public void run() {
            try {
                final BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                while (true) {
                    output.put(jsonParser.readValue(reader.readLine(), Map.class));
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

}
